// The initial set of tests were taken from Deno fetch unit test suite
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// https://github.com/denoland/deno/blob/main/LICENSE.md
import { wrapInTests } from "./js_builtins/testHelpers";
import { assert, expect } from "chai";
import { action, ActionCtx, query } from "./_generated/server";
import { api } from "./_generated/api";

export const fromQuery = query({
  args: {},
  handler: async () => {
    await fetch("http://localhost:4545/assets/fixture.json");
  },
});

export const checkForAbort = query({
  args: {},
  handler: async (ctx) => {
    const abort = await ctx.db.query("triggerAbort" as any).first();
    return abort !== null;
  },
});

async function fetchAbortTest(ctx: ActionCtx) {
  const controller = new AbortController();
  void (async () => {
    while (!(await ctx.runQuery(api.fetch.checkForAbort))) {
      await new Promise((resolve) => setTimeout(resolve, 10));
    }
    controller.abort();
  })();
  const responseFetch = fetch("http://localhost:4548/pause", {
    method: "GET",
    signal: controller.signal,
  });
  await expect(responseFetch).to.be.rejectedWith("AbortError");
}

export const fetchAbort = action({
  args: {},
  handler: async (ctx) => {
    return await wrapInTests({
      fetchAbortTest: () => fetchAbortTest(ctx),
    });
  },
});

export default action(async () => {
  return await wrapInTests({
    fetchConstructorClones,
    fetchRequiresOneArgument,
    fetchProtocolError,
    fetchDnsError,
    fetchInvalidUriError,
    fetchMalformedUriError,
    fetchJson,
    fetchURL,
    fetchHeaders,
    fetchBlob,
    fetchBodyUsedReader,
    // This does not currently pass because we're not tracking this state between
    // the Response and the ReadableStream
    // fetchBodyUsedCancelStream,
    fetchAsyncIterator,
    fetchBodyReader,
    fetchBodyReaderBigBody,
    fetchBodyReaderMultiPartBody,
    responseClone,
    fetchMultipartFormDataSuccess,
    fetchMultipartFormBadContentType,
    fetchURLEncodedFormDataSuccess,
    fetchInitFormDataBinaryFileBody,
    fetchInitFormDataMultipleFilesBody,
    fetchWithRedirection,
    // fetchWithRelativeRedirection,
    fetchRedirectPostToGet,
    fetchWithRelativeRedirectionUrl,
    fetchWithInfRedirection,
    fetchCrossOriginRedirectStripsAuthorization,
    fetchRedirectBody,
    // fetchRedirectBodyStream,
    fetchInitStringBody,
    fetchRequestInitStringBody,
    fetchSeparateInit,
    fetchInitTypedArrayBody,
    fetchInitArrayBufferBody,
    fetchInitURLSearchParamsBody,
    fetchInitBlobBody,
    fetchInitFormDataBody,
    fetchInitFormDataBlobFilenameBody,
    fetchInitFormDataTextFileBody,
    fetchUserAgent,
    // fetchRequest,
    // fetchRequestAcceptHeaders,
    // fetchPostBodyString,
    // fetchPostBodyTypedArray,
    // fetchUserSetContentLength,
    // fetchUserSetTransferEncoding,
    // fetchWithNonAsciiRedirection,
    fetchWithManualRedirection,
    fetchWithErrorRedirection,
    responseRedirect,
    responseRedirectTakeURLObjectAsParameter,
    responseWithoutBody,
    fetchBodyReadTwice,
    fetchBodyReaderAfterRead,
    fetchBodyReaderWithCancelAndNewReader,
    fetchBodyReaderWithReadCancelAndNewReader,
    // fetchResourceCloseAfterStreamCancel,
    // fetchNullBodyStatus,
    fetchResponseContentLength,
    // fetchResponseConstructorNullBody,
    // fetchResponseConstructorInvalidStatus,
    // fetchResponseEmptyConstructor,
    // fetchCustomHttpClientParamCertificateSuccess,
    // fetchPostBodyReadableStream,
    // fetchWritableRespProps,
    // fetchFilterOutCustomHostHeader
    // fetchHeadRespBody,
    // fetchClientCertWrongPrivateKey,
    // fetchClientCertBadPrivateKey,
    // fetchClientCertNotPrivateKey,
    // fetchCustomClientPrivateKey,
    // fetchAbortWhileUploadStreaming,
    // fetchAbortWhileUploadStreamingWithReason,
    // fetchAbortWhileUploadStreamingWithPrimitiveReason,
    // fetchHeaderValueShouldNotPanic,
    // fetchHeaderNameShouldNotPanic,
    // fetchSupportsHttp1Only,
    // fetchSupportsHttp2,
    // fetchPrefersHttp2,
    // fetchFilePerm,
    // fetchFilePermDoesNotExist,
    // fetchFileBadMethod,
    // fetchFileDoesNotExist,
    // fetchFile,
    // fetchContentLengthPost,
    // fetchContentLengthPut,
    // fetchContentLengthPatch,
    // fetchContentLengthPostWithStringBody,
    // fetchContentLengthPostWithBufferBody,
    // staticResponseJson,
    // fetchWithInvalidContentLengthAndTransferEncoding,
    // fetchWithInvalidContentLength,
    // fetchWithInvalidContentLength2,
    // fetchBlobUrl,
    fetchResponseStreamIsLockedWhileReading,
    fetchResponseStreamIsLockedWhileReadingBlob,
    fetchForbidden,
    fetchOlaf,
    fetchBodyTextDecoderStream,
  });
});

async function fetchRequiresOneArgument() {
  await expect(
    // @ts-expect-error intentionally pass no arguments
    fetch(),
  ).to.be.rejectedWith(TypeError, "Request URL is undefined");
}

async function fetchProtocolError() {
  await expect(fetch("ftp://localhost:21/a/file")).to.be.rejectedWith(
    TypeError,
    "Unsupported URL scheme",
  );
}

async function fetchDnsError() {
  await expect(fetch("http://invalid/")).to.be.rejectedWith(
    /dns error: failed to lookup address information/,
  );
}

async function fetchInvalidUriError() {
  await expect(fetch("http://<invalid>/")).to.be.rejectedWith(/Invalid URL/);
}

async function fetchMalformedUriError() {
  const url = new URL("http://{{google/");
  await expect(fetch(url)).to.be.rejectedWith(/Parsed Url is not a valid Uri/);
}

async function fetchJson() {
  const response = await fetch("http://localhost:4545/assets/fixture.json");
  assert.strictEqual(response.ok, true);
  assert.strictEqual(response.status, 200);
  assert.strictEqual(response.statusText, "OK");
  assert.strictEqual(response.type, "basic");
  const json = await response.json();
  assert.strictEqual(json.name, "convex");
}

async function fetchURL() {
  const response = await fetch(
    new URL("http://localhost:4545/assets/fixture.json"),
  );
  assert.strictEqual(response.url, "http://localhost:4545/assets/fixture.json");
  const _json = await response.json();
}

async function fetchHeaders() {
  const response = await fetch("http://localhost:4545/assets/fixture.json");
  const headers = response.headers;
  assert.strictEqual(headers.get("Content-Type"), "application/json");
  const _json = await response.json();
}

async function fetchBlob() {
  const response = await fetch("http://localhost:4545/assets/fixture.json");
  const headers = response.headers;
  const blob = await response.blob();
  assert.strictEqual(blob.type, headers.get("Content-Type"));
  assert.strictEqual(blob.size, Number(headers.get("Content-Length")));
  assert.strictEqual(await blob.text(), '{"name":"convex"}');
}

async function fetchBodyUsedReader() {
  const response = await fetch("http://localhost:4545/assets/fixture.json");
  assert(response.body !== null);

  const reader = response.body!.getReader();
  // Getting a reader should lock the stream but does not consume the body
  // so bodyUsed should not be true
  assert.strictEqual(response.bodyUsed, false);
  reader.releaseLock();
  await response.json();
  assert.strictEqual(response.bodyUsed, true);
}

// async function fetchBodyUsedCancelStream() {
//   const response = await fetch("http://localhost:4545/assets/fixture.json");
//   assert(response.body !== null);

//   assert.strictEqual(response.bodyUsed, false);
//   const promise = response.body.cancel();
//   assert.strictEqual(response.bodyUsed, true);
//   await promise;
// }

async function fetchAsyncIterator() {
  const response = await fetch("http://localhost:4545/assets/fixture.json");
  const headers = response.headers;

  assert(response.body !== null);
  let total = 0;
  // @ts-expect-error Type 'ReadableStream<Uint8Array>' must have a '[Symbol.asyncIterator]()' method that returns an async iterator.
  for await (const chunk of response.body) {
    assert(chunk instanceof Uint8Array);
    total += chunk.length;
  }

  assert.strictEqual(total, Number(headers.get("Content-Length")));
}

async function fetchBodyReader() {
  const response = await fetch("http://localhost:4545/assets/fixture.json");
  const headers = response.headers;
  assert(response.body !== null);
  const reader = response.body!.getReader();
  let total = 0;
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    assert(value);
    assert(value instanceof Uint8Array);
    total += value.length;
  }

  assert.strictEqual(total, Number(headers.get("Content-Length")));
}

async function fetchBodyReaderBigBody() {
  const data = "a".repeat(10 << 10); // 10mb
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: data,
  });
  assert(response.body !== null);
  const reader = await response.body.getReader();
  let total = 0;
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    assert(value);
    total += value.length;
  }

  assert.strictEqual(total, data.length);
}

// Covers streaming fetch, because /echo_server returns the request stream
// as the response stream.
async function fetchBodyReaderMultiPartBody() {
  const encoder = new TextEncoder();
  const decoder = new TextDecoder();
  const { readable, writable } = new TransformStream();
  const writer = writable.getWriter();
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: readable,
  });
  // Note we're doing full-duplex streaming, with the response status coming
  // back before the request body has started sending.
  assert(response.ok);
  assert(response.body !== null);
  const reader = response.body.getReader();

  await writer.write(encoder.encode("Hello "));
  const chunk1 = await reader.read();
  assert.strictEqual(decoder.decode(chunk1.value), "Hello ");
  assert.strictEqual(chunk1.done, false);

  await writer.write(encoder.encode("World!"));
  const chunk2 = await reader.read();
  assert.strictEqual(decoder.decode(chunk2.value), "World!");
  assert.strictEqual(chunk2.done, false);

  await writer.close();
  const chunk3 = await reader.read();
  assert.strictEqual(chunk3.value, undefined);
  assert.strictEqual(chunk3.done, true);
}

async function responseClone() {
  const response = await fetch("http://localhost:4545/assets/fixture.json");
  const response1 = response.clone();
  assert(response !== response1);
  assert.strictEqual(response.status, response1.status);
  assert.strictEqual(response.statusText, response1.statusText);
  const body = await response.arrayBuffer();
  const body1 = await response1.arrayBuffer();
  assert.deepEqual(body, body1);
}

async function fetchMultipartFormDataSuccess() {
  const response = await fetch("http://localhost:4545/multipart_form_data.txt");
  const formData = await response.formData();
  assert(formData.has("field_1"));
  assert.strictEqual(formData.get("field_1")!.toString(), "value_1 \r\n");
  assert(formData.has("field_2"));
  const file = formData.get("field_2") as File;
  assert.strictEqual(file.name, "file.js");

  assert.strictEqual(await file.text(), `console.log("Hi")`);
}

async function fetchMultipartFormBadContentType() {
  const response = await fetch(
    "http://localhost:4545/multipart_form_bad_content_type",
  );
  // assert(response.body !== null);

  await expect(response.formData()).to.be.rejectedWith(
    TypeError,
    "Body cannot be decoded as form data",
  );
}

async function fetchURLEncodedFormDataSuccess() {
  const response = await fetch(
    "http://localhost:4545/subdir/form_urlencoded.txt",
  );
  const formData = await response.formData();
  assert(formData.has("field_1"));
  assert.strictEqual(formData.get("field_1")!.toString(), "Hi");
  assert(formData.has("field_2"));
  assert.strictEqual(formData.get("field_2")!.toString(), "<Convex>");
}

async function fetchInitFormDataBinaryFileBody() {
  // Some random bytes
  const binaryFile = new Uint8Array([
    108, 2, 0, 0, 145, 22, 162, 61, 157, 227, 166, 77, 138, 75, 180, 56, 119,
    188, 177, 183,
  ]);
  const response = await fetch("http://localhost:4545/echo_multipart_file", {
    method: "POST",
    body: binaryFile,
  });
  const resultForm = await response.formData();
  const resultFile = resultForm.get("file") as File;

  assert.strictEqual(resultFile.type, "application/octet-stream");
  assert.strictEqual(resultFile.name, "file.bin");
  assert.deepEqual(new Uint8Array(await resultFile.arrayBuffer()), binaryFile);
}

async function fetchInitFormDataMultipleFilesBody() {
  const files = [
    {
      content: new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10, 137, 1, 25]),
      type: "image/png",
      name: "image",
      fileName: "some-image.png",
    },
    {
      content: new Uint8Array([
        108, 2, 0, 0, 145, 22, 162, 61, 157, 227, 166, 77, 138, 75, 180, 56,
        119, 188, 177, 183,
      ]),
      name: "file",
      fileName: "file.bin",
      expectedType: "application/octet-stream",
    },
    {
      content: new TextEncoder().encode("deno land"),
      type: "text/plain",
      name: "text",
      fileName: "deno.txt",
    },
  ];
  const form = new FormData();
  form.append("field", "value");
  for (const file of files) {
    form.append(
      file.name,
      new Blob([file.content], { type: file.type }),
      file.fileName,
    );
  }
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: form,
  });
  const resultForm = await response.formData();
  assert.strictEqual(form.get("field"), resultForm.get("field"));
  for (const file of files) {
    const inputFile = form.get(file.name) as File;
    const resultFile = resultForm.get(file.name) as File;
    assert.strictEqual(inputFile.size, resultFile.size);
    assert.strictEqual(inputFile.name, resultFile.name);
    assert.strictEqual(file.expectedType || file.type, resultFile.type);
    assert.deepEqual(
      new Uint8Array(await resultFile.arrayBuffer()),
      file.content,
    );
  }
}

async function fetchWithRedirection() {
  const response = await fetch("http://localhost:4545/assets/hello.txt");
  assert.strictEqual(response.status, 200);
  assert.strictEqual(response.statusText, "OK");
  assert.strictEqual(response.url, "http://localhost:4545/assets/fixture.json");
  const body = await response.text();
  assert.strictEqual(body, '{"name":"convex"}');
}

// async function fetchWithRelativeRedirection() {
//   const response = await fetch("http://localhost:4545/run/001_hello.js");
//   assert.strictEqual(response.status, 200);
//   assert.strictEqual(response.statusText, "OK");
//   const body = await response.text();
//   assert(body.includes("Hello"));
// }

async function fetchCrossOriginRedirectStripsAuthorization() {
  const headers = {
    authorization: "Bearer convex-admin",
    "x-location": "http://localhost:4547/print_auth",
  };
  const directResponse = await fetch("http://localhost:4547/print_auth", {
    headers,
  });
  assert.strictEqual(directResponse.status, 200);
  const directBody = await directResponse.text();
  assert.strictEqual(directBody, '{"auth":"Bearer convex-admin"}');

  const response = await fetch("http://localhost:4545/assets/hello.txt", {
    headers,
  });
  assert.strictEqual(response.status, 200);
  assert.strictEqual(response.url, "http://localhost:4547/print_auth");
  const body = await response.text();
  assert.strictEqual(body, '{"auth":"None"}');
}

async function fetchRedirectBody() {
  const response = await fetch("http://localhost:4545/redirect_body", {
    method: "POST",
    body: "Should be forwarded",
  });
  assert.strictEqual(response.status, 200);
  assert.strictEqual(response.url, "http://localhost:4545/echo_server");
  const body = await response.text();
  assert.strictEqual(body, "Should be forwarded");
}

// // This test should pass, but it flakes very infrequently, for reasons that
// // look like a race in the hyper library?
// // When it flakes, the error is:
// expected promise to be rejected with 'TypeError' but it was rejected with
// 'Error: error sending request for url (http://localhost:4545/redirect_body):
// error writing a body to connection: Broken pipe (os error 32)'
//
// async function fetchRedirectBodyStream() {
//   const stream = new Blob(["Should not be forwarded"]).stream();
//   await expect(
//     fetch("http://localhost:4545/redirect_body", {
//       method: "POST",
//       body: stream,
//     })
//   ).to.be.rejectedWith(TypeError, "fetch cannot redirect with streamed body");
// }

async function fetchRedirectPostToGet() {
  const response = await fetch("http://localhost:4545/post_redirect_to_get", {
    method: "POST",
    headers: { "x-location": "/assets/fixture.json" },
  });
  assert.strictEqual(response.status, 200);
  assert.strictEqual(response.url, "http://localhost:4545/assets/fixture.json");
  const body = await response.text();
  assert(body.includes('{"name":"convex"}'));
}

async function fetchWithRelativeRedirectionUrl() {
  const cases = [
    ["end", "http://localhost:4545/a/b/end"],
    ["/end", "http://localhost:4545/end"],
  ];
  for (const [loc, redUrl] of cases) {
    const response = await fetch("http://localhost:4545/a/b/c", {
      headers: new Headers([["x-location", loc]]),
    });
    assert.strictEqual(response.url, redUrl);
    assert.strictEqual(response.redirected, true);
    assert.strictEqual(response.status, 404);
    assert.strictEqual(await response.text(), "");
  }
}

async function fetchWithInfRedirection() {
  await expect(
    fetch("http://localhost:4545/assets/hello.txt", {
      headers: { "x-location": "/assets/hello.txt" },
    }),
  ).to.be.rejectedWith(TypeError, "redirect");
}

async function fetchInitStringBody() {
  const data = "Hello World";
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: data,
  });
  const text = await response.text();
  assert.strictEqual(text, data);
  assert(response.headers.get("content-type")!.startsWith("text/plain"));
}

async function fetchRequestInitStringBody() {
  const data = "Hello World";
  const req = new Request("http://localhost:4545/echo_server", {
    method: "POST",
    body: data,
  });
  const response = await fetch(req);
  const text = await response.text();
  assert.strictEqual(text, data);
}

async function fetchSeparateInit() {
  // related to: https://github.com/denoland/deno/issues/10396
  const req = new Request("http://localhost:4545/assets/fixture.json");
  const init = {
    method: "GET",
  };
  req.headers.set("foo", "bar");
  const res = await fetch(req, init);
  assert.strictEqual(res.status, 200);
  await res.text();
}

async function fetchInitTypedArrayBody() {
  const data = "Hello World";
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: new TextEncoder().encode(data),
  });
  const text = await response.text();
  assert.strictEqual(text, data);
}

async function fetchInitArrayBufferBody() {
  const data = "Hello World";
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: new TextEncoder().encode(data).buffer,
  });
  const text = await response.text();
  assert.strictEqual(text, data);
}

async function fetchInitURLSearchParamsBody() {
  const data = "param1=value1&param2=value2";
  const params = new URLSearchParams(data);
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: params,
  });
  const text = await response.text();
  assert.strictEqual(text, data);
  assert(
    response.headers
      .get("content-type")!
      .startsWith("application/x-www-form-urlencoded"),
  );
}

async function fetchInitBlobBody() {
  const data = "const a = 1";
  const blob = new Blob([data], {
    type: "text/javascript",
  });
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: blob,
  });
  const text = await response.text();
  assert.strictEqual(text, data);
  assert(response.headers.get("content-type")!.startsWith("text/javascript"));
}

async function fetchInitFormDataBody() {
  const form = new FormData();
  form.append("field", "value");
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: form,
  });
  const resultForm = await response.formData();
  assert.strictEqual(form.get("field"), resultForm.get("field"));
}

async function fetchInitFormDataBlobFilenameBody() {
  const form = new FormData();
  form.append("field", "value");
  form.append("file", new Blob([new TextEncoder().encode("convex")]));
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: form,
  });
  const resultForm = await response.formData();
  assert.strictEqual(form.get("field"), resultForm.get("field"));
  const file = resultForm.get("file");
  assert(file instanceof File);
  assert.strictEqual(file.name, "blob");
}

async function fetchInitFormDataTextFileBody() {
  const fileContent = "friends of convex";
  const form = new FormData();
  form.append("field", "value");
  form.append(
    "file",
    new Blob([new TextEncoder().encode(fileContent)], {
      type: "text/plain",
    }),
    "convex.txt",
  );
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: form,
  });
  const resultForm = await response.formData();
  assert.strictEqual(form.get("field"), resultForm.get("field"));

  const file = form.get("file") as File;
  const resultFile = resultForm.get("file") as File;

  assert.strictEqual(file.size, resultFile.size);
  assert.strictEqual(file.name, resultFile.name);
  assert.strictEqual(file.type, resultFile.type);
  assert.strictEqual(await file.text(), await resultFile.text());
}

async function fetchUserAgent() {
  const data = "Hello World";
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: new TextEncoder().encode(data),
  });
  assert.equal(response.headers.get("user-agent"), `Convex/1.0`);
  await response.text();
}

// function bufferServer(addr: string): Promise<Buffer> {
//   const [hostname, port] = addr.split(":");
//   const listener = Deno.listen({
//     hostname,
//     port: Number(port),
//   }) as Deno.Listener;
//   return listener.accept().then(async (conn: Deno.Conn) => {
//     const buf = new Buffer();
//     const p1 = buf.readFrom(conn);
//     const p2 = conn.write(
//       new TextEncoder().encode(
//         "HTTP/1.0 404 Not Found\r\nContent-Length: 2\r\n\r\nNF"
//       )
//     );
//     // Wait for both an EOF on the read side of the socket and for the write to
//     // complete before closing it. Due to keep-alive, the EOF won't be sent
//     // until the Connection close (HTTP/1.0) response, so readFrom() can't
//     // proceed write. Conversely, if readFrom() is async, waiting for the
//     // write() to complete is not a guarantee that we've read the incoming
//     // request.
//     await Promise.all([p1, p2]);
//     conn.close();
//     listener.close();
//     return buf;
//   });
// }

// async function fetchRequest() {
//   const addr = "127.0.0.1:4501";
//   const bufPromise = bufferServer(addr);
//   const response = await fetch(`http://${addr}/blah`, {
//     method: "POST",
//     headers: [
//       ["Hello", "World"],
//       ["Foo", "Bar"],
//     ],
//   });
//   await response.arrayBuffer();
//   assertEquals(response.status, 404);
//   assertEquals(response.headers.get("Content-Length"), "2");

//   const actual = new TextDecoder().decode((await bufPromise).bytes());
//   const expected = [
//     "POST /blah HTTP/1.1\r\n",
//     "content-length: 0\r\n",
//     "hello: World\r\n",
//     "foo: Bar\r\n",
//     "accept: */*\r\n",
//     "accept-language: *\r\n",
//     `user-agent: Deno/${Deno.version.deno}\r\n`,
//     "accept-encoding: gzip, br\r\n",
//     `host: ${addr}\r\n\r\n`,
//   ].join("");
//   assertEquals(actual, expected);
// }

// async function fetchRequestAcceptHeaders() {
//   const addr = "127.0.0.1:4501";
//   const bufPromise = bufferServer(addr);
//   const response = await fetch(`http://${addr}/blah`, {
//     method: "POST",
//     headers: [
//       ["Accept", "text/html"],
//       ["Accept-Language", "en-US"],
//     ],
//   });
//   await response.arrayBuffer();
//   assertEquals(response.status, 404);
//   assertEquals(response.headers.get("Content-Length"), "2");

//   const actual = new TextDecoder().decode((await bufPromise).bytes());
//   const expected = [
//     "POST /blah HTTP/1.1\r\n",
//     "content-length: 0\r\n",
//     "accept: text/html\r\n",
//     "accept-language: en-US\r\n",
//     `user-agent: Deno/${Deno.version.deno}\r\n`,
//     "accept-encoding: gzip, br\r\n",
//     `host: ${addr}\r\n\r\n`,
//   ].join("");
//   assertEquals(actual, expected);
// }

// async function fetchPostBodyString() {
//   const addr = "127.0.0.1:4511";
//   const bufPromise = bufferServer(addr);
//   const body = "hello world";
//   const response = await fetch(`http://${addr}/blah`, {
//     method: "POST",
//     headers: [
//       ["Hello", "World"],
//       ["Foo", "Bar"],
//     ],
//     body,
//   });
//   await response.arrayBuffer();
//   assertEquals(response.status, 404);
//   assertEquals(response.headers.get("Content-Length"), "2");

//   const actual = new TextDecoder().decode((await bufPromise).bytes());
//   const expected = [
//     "POST /blah HTTP/1.1\r\n",
//     "hello: World\r\n",
//     "foo: Bar\r\n",
//     "content-type: text/plain;charset=UTF-8\r\n",
//     "accept: */*\r\n",
//     "accept-language: *\r\n",
//     `user-agent: Deno/${Deno.version.deno}\r\n`,
//     "accept-encoding: gzip, br\r\n",
//     `host: ${addr}\r\n`,
//     `content-length: ${body.length}\r\n\r\n`,
//     body,
//   ].join("");
//   assertEquals(actual, expected);
// }

// async function fetchPostBodyTypedArray() {
//   const addr = "127.0.0.1:4503";
//   const bufPromise = bufferServer(addr);
//   const bodyStr = "hello world";
//   const body = new TextEncoder().encode(bodyStr);
//   const response = await fetch(`http://${addr}/blah`, {
//     method: "POST",
//     headers: [
//       ["Hello", "World"],
//       ["Foo", "Bar"],
//     ],
//     body,
//   });
//   await response.arrayBuffer();
//   assertEquals(response.status, 404);
//   assertEquals(response.headers.get("Content-Length"), "2");

//   const actual = new TextDecoder().decode((await bufPromise).bytes());
//   const expected = [
//     "POST /blah HTTP/1.1\r\n",
//     "hello: World\r\n",
//     "foo: Bar\r\n",
//     "accept: */*\r\n",
//     "accept-language: *\r\n",
//     `user-agent: Deno/${Deno.version.deno}\r\n`,
//     "accept-encoding: gzip, br\r\n",
//     `host: ${addr}\r\n`,
//     `content-length: ${body.byteLength}\r\n\r\n`,
//     bodyStr,
//   ].join("");
//   assertEquals(actual, expected);
// }

// async function fetchUserSetContentLength() {
//   const addr = "127.0.0.1:4501";
//   const bufPromise = bufferServer(addr);
//   const response = await fetch(`http://${addr}/blah`, {
//     method: "POST",
//     headers: [["Content-Length", "10"]],
//   });
//   await response.arrayBuffer();
//   assertEquals(response.status, 404);
//   assertEquals(response.headers.get("Content-Length"), "2");

//   const actual = new TextDecoder().decode((await bufPromise).bytes());
//   const expected = [
//     "POST /blah HTTP/1.1\r\n",
//     "content-length: 0\r\n",
//     "accept: */*\r\n",
//     "accept-language: *\r\n",
//     `user-agent: Deno/${Deno.version.deno}\r\n`,
//     "accept-encoding: gzip, br\r\n",
//     `host: ${addr}\r\n\r\n`,
//   ].join("");
//   assertEquals(actual, expected);
// }

// async function fetchUserSetTransferEncoding() {
//   const addr = "127.0.0.1:4501";
//   const bufPromise = bufferServer(addr);
//   const response = await fetch(`http://${addr}/blah`, {
//     method: "POST",
//     headers: [["Transfer-Encoding", "chunked"]],
//   });
//   await response.arrayBuffer();
//   assertEquals(response.status, 404);
//   assertEquals(response.headers.get("Content-Length"), "2");

//   const actual = new TextDecoder().decode((await bufPromise).bytes());
//   const expected = [
//     "POST /blah HTTP/1.1\r\n",
//     "content-length: 0\r\n",
//     `host: ${addr}\r\n`,
//     "accept: */*\r\n",
//     "accept-language: *\r\n",
//     `user-agent: Deno/${Deno.version.deno}\r\n`,
//     "accept-encoding: gzip, br\r\n\r\n",
//   ].join("");
//   assertEquals(actual, expected);
// }

// async function fetchWithNonAsciiRedirection() {
//   const response = await fetch("http://localhost:4545/non_ascii_redirect", {
//     redirect: "manual",
//   });
//   assert.strictEqual(response.status, 301);
//   assert.strictEqual(response.headers.get("location"), "/redirect®");
//   await response.text();
// }

async function fetchWithManualRedirection() {
  const response = await fetch("http://localhost:4545/assets/hello.txt", {
    redirect: "manual",
  });
  assert.strictEqual(response.status, 301);
  assert.strictEqual(response.statusText, "Moved Permanently");
  assert.strictEqual(response.url, "http://localhost:4545/assets/hello.txt");
  assert.strictEqual(response.type, "basic");
  assert.strictEqual(response.headers.get("Location"), "/assets/fixture.json");
  await response.body!.cancel();
}

async function fetchWithErrorRedirection() {
  await expect(
    fetch("http://localhost:4545/assets/hello.txt", {
      redirect: "error",
    }),
  ).to.be.rejectedWith(TypeError, "redirect");
}

function responseRedirect() {
  const redir = Response.redirect("http://example.com/newLocation", 301);
  assert.strictEqual(redir.status, 301);
  assert.strictEqual(redir.statusText, "");
  assert.strictEqual(redir.url, "");
  assert.strictEqual(
    redir.headers.get("Location"),
    "http://example.com/newLocation",
  );
  assert.strictEqual(redir.type, "default");
}

function responseRedirectTakeURLObjectAsParameter() {
  const redir = Response.redirect(new URL("https://example.com/"));
  assert.strictEqual(redir.headers.get("Location"), "https://example.com/");
}

async function responseWithoutBody() {
  const response = new Response();
  assert.deepEqual(await response.arrayBuffer(), new ArrayBuffer(0));
  const blob = await response.blob();
  assert.strictEqual(blob.size, 0);
  assert.deepEqual(await blob.arrayBuffer(), new ArrayBuffer(0));
  assert.strictEqual(await response.text(), "");
  await expect(response.json()).to.be.rejectedWith(
    "Unexpected end of JSON input",
  );
}

async function fetchBodyReadTwice() {
  const response = await fetch("http://localhost:4545/assets/fixture.json");

  // Read body
  const _json = await response.json();
  assert(_json);

  // All calls after the body was consumed, should fail
  const methods = ["json", "text", /*"formData",*/ "arrayBuffer"] as const;
  for (const method of methods) {
    try {
      await response[method]();
      assert(
        false,
        "Reading body multiple times should failed, the stream should've been locked.",
      );
    } catch {
      // pass
    }
  }
}

async function fetchBodyReaderAfterRead() {
  const response = await fetch("http://localhost:4545/assets/fixture.json");
  assert(response.body !== null);
  const reader = await response.body.getReader();
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    assert(value);
  }

  assert.throws(
    () => response.body!.getReader(),
    "This stream has already been locked",
  );
}

async function fetchBodyReaderWithCancelAndNewReader() {
  const data = "a".repeat(1 << 10);
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: data,
  });
  assert(response.body !== null);
  const firstReader = await response.body.getReader();

  // Acquire reader without reading & release
  await firstReader.releaseLock();

  const reader = await response.body.getReader();

  let total = 0;
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    assert(value);
    total += value.length;
  }

  assert.strictEqual(total, data.length);
}

async function fetchBodyReaderWithReadCancelAndNewReader() {
  const data = "a".repeat(1 << 10);

  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: data,
  });
  assert(response.body !== null);
  const firstReader = await response.body.getReader();

  // Do one single read with first reader
  const { value: firstValue } = await firstReader.read();
  assert(firstValue);
  await firstReader.releaseLock();

  // Continue read with second reader
  const reader = await response.body.getReader();
  let total = firstValue.length || 0;
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    assert(value);
    total += value.length;
  }
  assert.strictEqual(total, data.length);
}

// async function fetchResourceCloseAfterStreamCancel() {
//   const res = await fetch("http://localhost:4545/assets/fixture.json");
//   assert(res.body !== null);

//   // After ReadableStream.cancel is called, resource handle must be closed
//   // The test should not fail with: Test case is leaking resources
//   await res.body.cancel();
// }

// async function fetchNullBodyStatus() {
//   const nullBodyStatus = [101, 204, 205, 304];

//   for (const status of nullBodyStatus) {
//     const headers = new Headers([["x-status", String(status)]]);
//     const res = await fetch("http://localhost:4545/echo_server", {
//       body: "deno",
//       method: "POST",
//       headers,
//     });
//     assertEquals(res.body, null);
//     assertEquals(res.status, status);
//   }
// }

async function fetchResponseContentLength() {
  const body = new Uint8Array(2 ** 16);
  const headers = new Headers([["content-type", "application/octet-stream"]]);
  const res = await fetch("http://localhost:4545/echo_server", {
    body: body,
    method: "POST",
    headers,
  });
  assert.strictEqual(
    Number(res.headers.get("content-length")),
    body.byteLength,
  );

  const blob = await res.blob();
  // Make sure Body content-type is correctly set
  assert.strictEqual(blob.type, "application/octet-stream");
  assert.strictEqual(blob.size, body.byteLength);
}

// function fetchResponseConstructorNullBody() {
//   const nullBodyStatus = [204, 205, 304];

//   for (const status of nullBodyStatus) {
//     try {
//       new Response("deno", { status });
//       fail("Response with null body status cannot have body");
//     } catch (e) {
//       assert(e instanceof TypeError);
//       assertEquals(
//         e.message,
//         "Response with null body status cannot have body"
//       );
//     }
//   }
// }

// function fetchResponseConstructorInvalidStatus() {
//   const invalidStatus = [100, 600, 199, null, "", NaN];

//   for (const status of invalidStatus) {
//     try {
//       // deno-lint-ignore ban-ts-comment
//       // @ts-ignore
//       new Response("deno", { status });
//       fail(`Invalid status: ${status}`);
//     } catch (e) {
//       assert(e instanceof RangeError);
//       assert(
//         e.message.endsWith(
//           "is not equal to 101 and outside the range [200, 599]."
//         )
//       );
//     }
//   }
// }

// function fetchResponseEmptyConstructor() {
//   const response = new Response();
//   assertEquals(response.status, 200);
//   assertEquals(response.body, null);
//   assertEquals(response.type, "default");
//   assertEquals(response.url, "");
//   assertEquals(response.redirected, false);
//   assertEquals(response.ok, true);
//   assertEquals(response.bodyUsed, false);
//   assertEquals([...response.headers], []);
// }

// async function fetchCustomHttpClientParamCertificateSuccess(): Promise<void> {
//   const caCert = Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem");
//   const client = Deno.createHttpClient({ caCerts: [caCert] });
//   const response = await fetch("https://localhost:5545/assets/fixture.json", {
//     client,
//   });
//   const json = await response.json();
//   assertEquals(json.name, "deno");
//   client.close();
// }

// async function fetchCustomClientUserAgent(): Promise<void> {
//   const data = "Hello World";
//   const client = Deno.createHttpClient({});
//   const response = await fetch("http://localhost:4545/echo_server", {
//     client,
//     method: "POST",
//     body: new TextEncoder().encode(data),
//   });
//   assertEquals(response.headers.get("user-agent"), `Deno/${Deno.version.deno}`);
//   await response.text();
//   client.close();
// }

// async function fetchPostBodyReadableStream() {
//   const addr = "127.0.0.1:4511";
//   const bufPromise = bufferServer(addr);
//   const stream = new TransformStream();
//   const writer = stream.writable.getWriter();
//   // transformer writes don't resolve until they are read, so awaiting these
//   // will cause the transformer to hang, as the suspend the transformer, it
//   // is also illogical to await for the reads, as that is the whole point of
//   // streams is to have a "queue" which gets drained...
//   writer.write(new TextEncoder().encode("hello "));
//   writer.write(new TextEncoder().encode("world"));
//   writer.close();
//   const response = await fetch(`http://${addr}/blah`, {
//     method: "POST",
//     headers: [
//       ["Hello", "World"],
//       ["Foo", "Bar"],
//     ],
//     body: stream.readable,
//   });
//   await response.arrayBuffer();
//   assertEquals(response.status, 404);
//   assertEquals(response.headers.get("Content-Length"), "2");

//   const actual = new TextDecoder().decode((await bufPromise).bytes());
//   const expected = [
//     "POST /blah HTTP/1.1\r\n",
//     "hello: World\r\n",
//     "foo: Bar\r\n",
//     "accept: */*\r\n",
//     "accept-language: *\r\n",
//     `user-agent: Deno/${Deno.version.deno}\r\n`,
//     "accept-encoding: gzip, br\r\n",
//     `host: ${addr}\r\n`,
//     `transfer-encoding: chunked\r\n\r\n`,
//     "6\r\n",
//     "hello \r\n",
//     "5\r\n",
//     "world\r\n",
//     "0\r\n\r\n",
//   ].join("");
//   assertEquals(actual, expected);
// }

// function fetchWritableRespProps() {
//   const original = new Response("https://deno.land", {
//     status: 404,
//     headers: { "x-deno": "foo" },
//   });
//   const new_ = new Response("https://deno.land", original);
//   assertEquals(original.status, new_.status);
//   assertEquals(new_.headers.get("x-deno"), "foo");
// }

// async function fetchFilterOutCustomHostHeader(): Promise<void> {
//   const addr = "127.0.0.1:4511";
//   const [hostname, port] = addr.split(":");
//   const listener = Deno.listen({
//     hostname,
//     port: Number(port),
//   }) as Deno.Listener;

//   let httpConn: Deno.HttpConn;
//   listener.accept().then(async (conn: Deno.Conn) => {
//     httpConn = Deno.serveHttp(conn);

//     await httpConn
//       .nextRequest()
//       .then(async (requestEvent: Deno.RequestEvent | null) => {
//         const hostHeader = requestEvent?.request.headers.get("Host");
//         const headersToReturn = hostHeader ? { Host: hostHeader } : undefined;

//         await requestEvent?.respondWith(
//           new Response("", {
//             status: 200,
//             headers: headersToReturn,
//           })
//         );
//       });
//   });

//   const response = await fetch(`http://${addr}/`, {
//     headers: { Host: "example.com" },
//   });
//   await response.text();
//   listener.close();
//   httpConn!.close();

//   assertEquals(response.headers.get("Host"), addr);
// }

// async function fetchNoServerReadableStreamBody() {
//   const done = deferred();
//   const body = new ReadableStream({
//     start(controller) {
//       controller.enqueue(new Uint8Array([1]));
//       setTimeout(() => {
//         controller.enqueue(new Uint8Array([2]));
//         done.resolve();
//       }, 1000);
//     },
//   });
//   const nonExistantHostname = "http://localhost:47582";
//   await assertRejects(async () => {
//     await fetch(nonExistantHostname, { body, method: "POST" });
//   }, TypeError);
//   await done;
// }

// async function fetchHeadRespBody() {
//   const res = await fetch("http://localhost:4545/echo_server", {
//     method: "HEAD",
//   });
//   assert.strictEqual(res.body, null);
// }

// async function fetchClientCertWrongPrivateKey(): Promise<void> {
//   await assertRejects(async () => {
//     const client = Deno.createHttpClient({
//       certChain: "bad data",
//       privateKey: await Deno.readTextFile(
//         "cli/tests/testdata/tls/localhost.key"
//       ),
//     });
//     await fetch("https://localhost:5552/assets/fixture.json", {
//       client,
//     });
//   }, Deno.errors.InvalidData);
// }

// async function fetchClientCertBadPrivateKey(): Promise<void> {
//   await assertRejects(async () => {
//     const client = Deno.createHttpClient({
//       certChain: await Deno.readTextFile(
//         "cli/tests/testdata/tls/localhost.crt"
//       ),
//       privateKey: "bad data",
//     });
//     await fetch("https://localhost:5552/assets/fixture.json", {
//       client,
//     });
//   }, Deno.errors.InvalidData);
// }

// async function fetchClientCertNotPrivateKey(): Promise<void> {
//   await assertRejects(async () => {
//     const client = Deno.createHttpClient({
//       certChain: await Deno.readTextFile(
//         "cli/tests/testdata/tls/localhost.crt"
//       ),
//       privateKey: "",
//     });
//     await fetch("https://localhost:5552/assets/fixture.json", {
//       client,
//     });
//   }, Deno.errors.InvalidData);
// }

// async function fetchCustomClientPrivateKey(): Promise<void> {
//   const data = "Hello World";
//   const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.crt");
//   const client = Deno.createHttpClient({
//     certChain: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"),
//     privateKey: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"),
//     caCerts: [caCert],
//   });
//   const response = await fetch("https://localhost:5552/echo_server", {
//     client,
//     method: "POST",
//     body: new TextEncoder().encode(data),
//   });
//   assertEquals(response.headers.get("user-agent"), `Deno/${Deno.version.deno}`);
//   await response.text();
//   client.close();
// }

// async function fetchAbortWhileUploadStreaming(): Promise<void> {
//   const abortController = new AbortController();
//   try {
//     await fetch("http://localhost:4545/echo_server", {
//       method: "POST",
//       body: new ReadableStream({
//         pull(_controller) {
//           abortController.abort();
//           // controller.enqueue(new Uint8Array([1, 2, 3, 4]));
//         },
//       }),
//       signal: abortController.signal,
//     });
//     expect.fail("Fetch didn't reject.");
//   } catch (error) {
//     expect(error).to.be.instanceOf(Error);
//     if (error instanceof Error) {
//       expect(error.message).to.equal("AbortError");
//     }
//     // expect(error).to.be.instanceOf(DOMException);
//     // expect(error.name).to.equal("AbortError");
//     // expect(error.message).to.equal("The signal has been aborted");
//   }
// }

// async function fetchAbortWhileUploadStreamingWithReason(): Promise<void> {
//   const abortController = new AbortController();
//   const abortReason = new Error();
//   try {
//     await fetch("http://localhost:5552/echo_server", {
//       method: "POST",
//       body: new ReadableStream({
//         pull(controller) {
//           abortController.abort(abortReason);
//           controller.enqueue(new Uint8Array([1, 2, 3, 4]));
//         },
//       }),
//       signal: abortController.signal,
//     });
//     fail("Fetch didn't reject.");
//   } catch (error) {
//     assertEquals(error, abortReason);
//   }
// }

// async function fetchAbortWhileUploadStreamingWithPrimitiveReason(): Promise<void> {
//   const abortController = new AbortController();
//   try {
//     await fetch("http://localhost:5552/echo_server", {
//       method: "POST",
//       body: new ReadableStream({
//         pull(controller) {
//           abortController.abort("Abort reason");
//           controller.enqueue(new Uint8Array([1, 2, 3, 4]));
//         },
//       }),
//       signal: abortController.signal,
//     });
//     fail("Fetch didn't reject.");
//   } catch (error) {
//     assertEquals(error, "Abort reason");
//   }
// }

// async function fetchHeaderValueShouldNotPanic() {
//   for (let i = 0; i < 0x21; i++) {
//     if (i === 0x09 || i === 0x0a || i === 0x0d || i === 0x20) {
//       continue; // these header value will be normalized, will not cause an error.
//     }
//     // ensure there will be an error instead of panic.
//     await assertRejects(
//       () =>
//         fetch("http://localhost:4545/echo_server", {
//           method: "HEAD",
//           headers: { val: String.fromCharCode(i) },
//         }),
//       TypeError
//     );
//   }
//   await assertRejects(
//     () =>
//       fetch("http://localhost:4545/echo_server", {
//         method: "HEAD",
//         headers: { val: String.fromCharCode(127) },
//       }),
//     TypeError
//   );
// }

// async function fetchHeaderNameShouldNotPanic() {
//   const validTokens =
//     "!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUWVXYZ^_`abcdefghijklmnopqrstuvwxyz|~".split(
//       ""
//     );
//   for (let i = 0; i <= 255; i++) {
//     const token = String.fromCharCode(i);
//     if (validTokens.includes(token)) {
//       continue;
//     }
//     // ensure there will be an error instead of panic.
//     await assertRejects(
//       () =>
//         fetch("http://localhost:4545/echo_server", {
//           method: "HEAD",
//           headers: { [token]: "value" },
//         }),
//       TypeError
//     );
//   }
//   await assertRejects(
//     () =>
//       fetch("http://localhost:4545/echo_server", {
//         method: "HEAD",
//         headers: { "": "value" },
//       }),
//     TypeError
//   );
// }

// async function fetchSupportsHttp1Only() {
//   const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem");
//   const client = Deno.createHttpClient({ caCerts: [caCert] });
//   const res = await fetch("https://localhost:5546/http_version", { client });
//   assert(res.ok);
//   assertEquals(await res.text(), "HTTP/1.1");
//   client.close();
// }

// async function fetchSupportsHttp2() {
//   const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem");
//   const client = Deno.createHttpClient({ caCerts: [caCert] });
//   const res = await fetch("https://localhost:5547/http_version", { client });
//   assert(res.ok);
//   assertEquals(await res.text(), "HTTP/2.0");
//   client.close();
// }

// async function fetchPrefersHttp2() {
//   const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem");
//   const client = Deno.createHttpClient({ caCerts: [caCert] });
//   const res = await fetch("https://localhost:5545/http_version", { client });
//   assert(res.ok);
//   assertEquals(await res.text(), "HTTP/2.0");
//   client.close();
// }

// async function fetchFilePerm() {
//   await assertRejects(async () => {
//     await fetch(import.meta.resolve("../testdata/subdir/json_1.json"));
//   }, Deno.errors.PermissionDenied);
// }

// async function fetchFilePermDoesNotExist() {
//   await assertRejects(async () => {
//     await fetch(import.meta.resolve("./bad.json"));
//   }, Deno.errors.PermissionDenied);
// }

// async function fetchFileBadMethod() {
//   await assertRejects(
//     async () => {
//       await fetch(import.meta.resolve("../testdata/subdir/json_1.json"), {
//         method: "POST",
//       });
//     },
//     TypeError,
//     "Fetching files only supports the GET method. Received POST."
//   );
// }

// async function fetchFileDoesNotExist() {
//   await assertRejects(async () => {
//     await fetch(import.meta.resolve("./bad.json"));
//   }, TypeError);
// }

// async function fetchFile() {
//   const res = await fetch(
//     import.meta.resolve("../testdata/subdir/json_1.json")
//   );
//   assert(res.ok);
//   const fixture = await Deno.readTextFile(
//     "cli/tests/testdata/subdir/json_1.json"
//   );
//   assertEquals(await res.text(), fixture);
// }

// async function fetchContentLengthPost() {
//   const response = await fetch("http://localhost:4545/content_length", {
//     method: "POST",
//   });
//   const length = await response.text();
//   assertEquals(length, 'Some("0")');
// }

// async function fetchContentLengthPut() {
//   const response = await fetch("http://localhost:4545/content_length", {
//     method: "PUT",
//   });
//   const length = await response.text();
//   assertEquals(length, 'Some("0")');
// }

// async function fetchContentLengthPatch() {
//   const response = await fetch("http://localhost:4545/content_length", {
//     method: "PATCH",
//   });
//   const length = await response.text();
//   assertEquals(length, "None");
// }

// async function fetchContentLengthPostWithStringBody() {
//   const response = await fetch("http://localhost:4545/content_length", {
//     method: "POST",
//     body: "Hey!",
//   });
//   const length = await response.text();
//   assertEquals(length, 'Some("4")');
// }

// async function fetchContentLengthPostWithBufferBody() {
//   const response = await fetch("http://localhost:4545/content_length", {
//     method: "POST",
//     body: new TextEncoder().encode("Hey!"),
//   });
//   const length = await response.text();
//   assertEquals(length, 'Some("4")');
// }

// async function staticResponseJson() {
//   const data = { hello: "world" };
//   const resp = Response.json(data);
//   assertEquals(resp.status, 200);
//   assertEquals(resp.headers.get("content-type"), "application/json");
//   const res = await resp.json();
//   assertEquals(res, data);
// }

// function invalidServer(addr: string, body: Uint8Array): Deno.Listener {
//   const [hostname, port] = addr.split(":");
//   const listener = Deno.listen({
//     hostname,
//     port: Number(port),
//   }) as Deno.Listener;

//   (async () => {
//     for await (const conn of listener) {
//       const p1 = conn.read(new Uint8Array(2 ** 14));
//       const p2 = conn.write(body);

//       await Promise.all([p1, p2]);
//       conn.close();
//     }
//   })();

//   return listener;
// }

// async function fetchWithInvalidContentLengthAndTransferEncoding(): Promise<void> {
//   const addr = "127.0.0.1:4516";
//   const data = "a".repeat(10 << 10);

//   const body = new TextEncoder().encode(
//     `HTTP/1.1 200 OK\r\nContent-Length: ${Math.round(
//       data.length * 2
//     )}\r\nTransfer-Encoding: chunked\r\n\r\n${data.length.toString(
//       16
//     )}\r\n${data}\r\n0\r\n\r\n`
//   );

//   // if transfer-encoding is sent, content-length is ignored
//   // even if it has an invalid value (content-length > totalLength)
//   const listener = invalidServer(addr, body);
//   const response = await fetch(`http://${addr}/`);

//   const res = await response.arrayBuffer();
//   const buf = new TextEncoder().encode(data);
//   assertEquals(res.byteLength, buf.byteLength);
//   assertEquals(new Uint8Array(res), buf);

//   listener.close();
// }

// async function fetchWithInvalidContentLength(): Promise<void> {
//   const addr = "127.0.0.1:4518";
//   const data = "a".repeat(10 << 10);

//   const contentLength = data.length / 2;
//   const body = new TextEncoder().encode(
//     `HTTP/1.1 200 OK\r\nContent-Length: ${contentLength}\r\n\r\n${data}`
//   );

//   const listener = invalidServer(addr, body);
//   const response = await fetch(`http://${addr}/`);

//   // If content-length < totalLength, a maximum of content-length bytes
//   // should be returned.
//   const res = await response.arrayBuffer();
//   const buf = new TextEncoder().encode(data);
//   assertEquals(res.byteLength, contentLength);
//   assertEquals(new Uint8Array(res), buf.subarray(contentLength));

//   listener.close();
// }

// async function fetchWithInvalidContentLength2(): Promise<void> {
//   const addr = "127.0.0.1:4519";
//   const data = "a".repeat(10 << 10);

//   const contentLength = data.length * 2;
//   const body = new TextEncoder().encode(
//     `HTTP/1.1 200 OK\r\nContent-Length: ${contentLength}\r\n\r\n${data}`
//   );

//   const listener = invalidServer(addr, body);
//   const response = await fetch(`http://${addr}/`);
//   // If content-length > totalLength, a maximum of content-length bytes
//   // should be returned.
//   await assertRejects(
//     async () => {
//       await response.arrayBuffer();
//     },
//     Error,
//     "end of file before message length reached"
//   );

//   listener.close();
// }

// async function fetchBlobUrl(): Promise<void> {
//   const blob = new Blob(["ok"], { type: "text/plain" });
//   const url = URL.createObjectURL(blob);
//   const res = await fetch(url);
//   assert(res.url.startsWith("blob:http://js-unit-tests/"));
//   assertEquals(res.status, 200);
//   assertEquals(res.headers.get("content-length"), "2");
//   assertEquals(res.headers.get("content-type"), "text/plain");
//   assertEquals(await res.text(), "ok");
// }

async function fetchResponseStreamIsLockedWhileReading() {
  const response = await fetch("http://localhost:4545/echo_server", {
    body: new Uint8Array(5000),
    method: "POST",
  });

  assert.strictEqual(response.body!.locked, false);
  const promise = response.arrayBuffer();
  assert.strictEqual(response.body!.locked, true);

  await promise;
}

async function fetchResponseStreamIsLockedWhileReadingBlob() {
  const response = await fetch("http://localhost:4545/echo_server", {
    body: new Uint8Array(5000),
    method: "POST",
  });

  assert.strictEqual(response.body!.locked, false);
  const promise = response.blob();
  assert.strictEqual(response.body!.locked, true);

  await promise;
}

async function fetchConstructorClones() {
  const req = new Request("https://example.com", {
    method: "POST",
    body: "foo",
  });
  assert.strictEqual(await req.text(), "foo");
  await expect(req.text()).to.be.rejectedWith(
    TypeError,
    /body stream already read/,
  );

  const req2 = new Request(req, { method: "PUT", body: "bar" }); // should not have any impact on req
  assert.strictEqual(await req2.text(), "bar");
  await expect(req2.text()).to.be.rejectedWith(
    TypeError,
    /body stream already read/,
  );

  assert.strictEqual(req.method, "POST");
  assert.strictEqual(req2.method, "PUT");

  assert.strictEqual(req.headers.get("x-foo"), null);
  assert.strictEqual(req2.headers.get("x-foo"), null);
  req2.headers.set("x-foo", "bar"); // should not have any impact on req
  assert.strictEqual(req.headers.get("x-foo"), null);
  assert.strictEqual(req2.headers.get("x-foo"), "bar");
}

async function fetchForbidden() {
  // TODO(presley): See if we can throw type error when we reject the promise
  // from rust
  await expect(fetch("http://localhost:4545/proxy_reject")).to.be.rejectedWith(
    "Request to http://localhost:4545/proxy_reject forbidden",
  );
}

export const fetchInParallel = action({
  args: {},
  handler: async () => {
    const parallelFetches = [
      fetch("http://localhost:4546/timeout"),
      fetch("http://localhost:4546/echo_server", {
        method: "POST",
        body: new TextEncoder().encode("hello world"),
      }),
    ];
    const response = await Promise.race(parallelFetches);
    assert(response.ok, await response.text());
  },
});

// Regression test.
export const fetchBlockedOnTimeouts = action({
  args: {},
  handler: async () => {
    const fetchWithTimeout = async () => {
      setTimeout(
        () => {
          // AbortController stuff.
          // This never runs.
        },
        10 * 60 * 1000,
      );
      await fetch("http://localhost:4546/echo_server", {
        method: "POST",
        body: new TextEncoder().encode("hello world"),
      });
    };
    // 10 setTimeouts queued up before the fetches.
    // When limited parallelism in actions applied to setTimeouts, the setTimeouts
    // would block the fetches.
    await Promise.all(
      Array(10)
        .fill(0)
        .map(() => fetchWithTimeout()),
    );
  },
});

export const danglingFetch = action({
  args: {},
  handler: () => {
    // eslint-disable-next-line @typescript-eslint/no-floating-promises
    fetch("http://localhost:4546/echo_server");
  },
});

export const fetchTimeout = action({
  args: {},
  handler: async () => {
    const request = new Request("http://localhost:4546/timeout");
    const result = await fetch(request);
    throw new Error(`fetch should not complete: ${result}`);
  },
});

export const fetchUnendingRequest = action({
  args: {},
  handler: async () => {
    const request = new Request("http://localhost:4546/echo_server", {
      method: "POST",
      body: new ReadableStream(),
    });
    const response = await fetch(request);
    await response.text();
    throw new Error(`fetch should not complete`);
  },
});

// Regression test for https://webtechsurvey.com/response-header/x-olaf
async function fetchOlaf() {
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    // hacky way to create a UTF-8 encoded byte string
    headers: { "X-Olaf": unescape(encodeURIComponent("⛄")) },
  });
  assert.strictEqual(response.headers.get("X-Olaf"), "â\x9B\x84");
}

async function fetchBodyTextDecoderStream() {
  const data = JSON.stringify({
    hello: "world",
    foo: "bar",
    baz: "qux",
  });
  const response = await fetch("http://localhost:4545/echo_server", {
    method: "POST",
    body: data,
  });
  assert(response.body !== null);
  const decodedStream = response.body!.pipeThrough(new TextDecoderStream());
  const reader = decodedStream.getReader();
  let decodedBody = "";
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    assert(value);
    decodedBody += value;
  }
  assert.strictEqual(decodedBody, data);
}
